/* 
 * Project: FlarDetector
 *
 * Copyright 2009, Longfellow Chen
 * --------------------------------------------------------------------------------
 * This work complements FLARToolkit, developed by Saqoosha as part of the Libspark project.
 *	http://www.libspark.org/wiki/saqoosha/FLARToolKit
 * FLARToolkit is Copyright (C)2008 Saqoosha,
 * and is ported from NYARToolkit, which is ported from ARToolkit.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this framework; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * For further information please contact:
 *	<fellong0307@gmail.com>
 *	http://fellong.blogspot.com/
 * 
 */
 
package com.fellong.flash.flar.detector
{
	import flash.display.BitmapData;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Timer;
	
	import org.libspark.flartoolkit.core.FLARCode;
	import org.libspark.flartoolkit.core.param.FLARParam;
	import org.libspark.flartoolkit.core.raster.rgb.FLARRgbRaster_BitmapData;
	import org.libspark.flartoolkit.core.transmat.FLARTransMatResult;
	import org.libspark.flartoolkit.detector.FLARMultiMarkerDetector;
	import org.libspark.flartoolkit.detector.FLARMultiMarkerDetectorResult;
	import org.libspark.flartoolkit.core.pickup.FLARDynamicRatioColorPatt_O3;
	import org.libspark.flartoolkit.core.FLARSquare;
	
	// **** in FLARMultiMarkerDetector, number_of_code seems NOT Necessary because 
	//		code Array has it's own length property, so I replaced "this._number_of_code"
	//		with "this._codes.length" at line 232 in FLARMultiMarkerDetector.as
	//		and because of this change, we can now resize our codes dynamic without re-new FLARMultiMarkerDetector
	//		see description below...

	public class FlarDetector extends EventDispatcher
	{
		/// _detectors array hole all registed FlarDetector
		private static var _detectors:Array = new Array();
		/// _codes array hole all registed marker pattern code
		private static var _codes:Array = new Array();
		/// _widths array hole all registed marker width
		private static var _widths:Array = new Array();
		/// _current_detected array hole all marker detected since last detectMarkerLite() call
		private static var _current_detected:Array = new Array();
		/// temporary variable
		private static var _last_detected:Array;
		private static var _temp_result:FLARTransMatResult = new FLARTransMatResult();

		/// camera param
		private static var _param:FLARParam;

		/// the core detector
		private static var _detector:FLARMultiMarkerDetector;
		
		/// camera bitmap raster
		private	static var _raster:FLARRgbRaster_BitmapData;
		
		/// number of square detected ( all possible marker )
		private static var _number_of_square:int;
		
		/// Events
		public static var MARKER_ADDED:String = "MARKER_ADDED";
		public static var MARKER_UPDATED:String = "MARKER_UPDATED";
		public static var MARKER_REMOVED:String = "MARKER_REMOVED";	
		public static var REGIST:String = "REGIST";
		public static var UNREGIST:String = "UNREGIST";

		/// _threshold: bitmap threshold
		public static var _threshold:int=80;
		
		///	_force_confidence: if set to 0, detector use it's own confidence steeing '_confidence'
		public static var _force_confidence:Number=0.5;

		/// _force_smooth: if set to false, detector use it's own smooth steeing
		public static var _force_smooth:Number=0.0;
		
		/// _force_delay: if set to 0, detector use it's own delay steeing
		public static var _force_delay:Number=0;

		/// marker pattern code FLARCode
		private var _code:FLARCode = null;
		
		/// transmat result
		private var _transmat:FLARTransMatResult = new FLARTransMatResult();
		
		/// detect reslut flag
		private var _detected:Boolean = false;
		
		/// detect result
		private var _result:FLARMultiMarkerDetectorResult;
		
		/// variable for code loader
		private var _loader:URLLoader;
		private var _i_width:Number;
		private var _i_markerPercentWidth:Number;
		
		/// marker option setting
		public var _delay:Number = 0;
		public var _confidence:Number = 0.5;
		public var _smooth:Number = 0.0;
		
		public function FlarDetector()
		{
		}
		
		public static function init( bitmapData:BitmapData ):void 
		{
			var value:String = "00000280000001e04085e79c9c75d24700000000000000004073c80000000000000000000000000000000000000000004086b0c0e24f8e96406e3000000000000000000000000000000000000000000000000000000000003ff000000000000000000000000000004073e800000000004070780000000000403a3333333333333ff034402e71210c";
			var len:uint = value.length/2;
			var ba:ByteArray = new ByteArray();
			for (var i:uint = 0; i < len; i++) {
				var c:String = value.charAt(i*2) + value.charAt(i*2 + 1);
				ba.writeByte(parseInt(c, 16));
			}
			ba.position = 0;
			_param = new FLARParam();
			_param.loadARParam(ba);
			_param.changeScreenSize(bitmapData.width, bitmapData.height);
			_raster = new FLARRgbRaster_BitmapData(bitmapData);
		}
		
		public static function get param():FLARParam
		{
			return _param;
		}
		
		public static function detectMarkerLite():void
		{
			var i:int = 0;
			try {
				i = _number_of_square = _detector.detectMarkerLite(_raster, _threshold);
			} catch (e:Error)
			{
				return;
			}
			
			var _last_detected:Array = _current_detected;
			_current_detected = new Array();

			while( i-- )
			{
				var the_detector:FlarDetector = _detectors[_detector.getARCodeIndex(i)] as FlarDetector;
				
				if( _force_confidence > 0 )
				{
					if( _detector.getConfidence(i) < _force_confidence )
						continue;
				} else
				{
					if( _detector.getConfidence(i) < the_detector._confidence )
						continue;
				}
				the_detector._result = _detector.getResult(i);
				
				try {
					the_detector._detected = true;	/// set detected flag
					
					var idx:int = _last_detected.indexOf( the_detector );
					if( ( the_detector._smooth >= 1 || _force_smooth >= 1 )&& idx >= 0 )		/// smooth control
					{
						_detector.getTransmationMatrix(i, _temp_result);
						/*
						the_detector._transmat.m00 = (the_detector._transmat.m00+_temp_result.m00)/2;
						the_detector._transmat.m01 = (the_detector._transmat.m01+_temp_result.m01)/2;
						the_detector._transmat.m02 = (the_detector._transmat.m02+_temp_result.m02)/2;
						the_detector._transmat.m03 = (the_detector._transmat.m03+_temp_result.m03)/2;
						the_detector._transmat.m10 = (the_detector._transmat.m10+_temp_result.m10)/2;
						the_detector._transmat.m11 = (the_detector._transmat.m11+_temp_result.m11)/2;
						the_detector._transmat.m12 = (the_detector._transmat.m12+_temp_result.m12)/2;
						the_detector._transmat.m13 = (the_detector._transmat.m13+_temp_result.m13)/2;
						the_detector._transmat.m20 = (the_detector._transmat.m20+_temp_result.m20)/2;
						the_detector._transmat.m21 = (the_detector._transmat.m21+_temp_result.m21)/2;
						the_detector._transmat.m22 = (the_detector._transmat.m22+_temp_result.m22)/2;
						the_detector._transmat.m23 = (the_detector._transmat.m23+_temp_result.m23)/2;
						*/
						var sl:Number = (_force_smooth >= 1) ? _force_smooth:the_detector._smooth;
						the_detector._transmat.m00 = the_detector._transmat.m00 - (the_detector._transmat.m00/sl) + (_temp_result.m00/sl);
						the_detector._transmat.m01 = the_detector._transmat.m01 - (the_detector._transmat.m01/sl) + (_temp_result.m01/sl);
						the_detector._transmat.m02 = the_detector._transmat.m02 - (the_detector._transmat.m02/sl) + (_temp_result.m02/sl);
						the_detector._transmat.m03 = the_detector._transmat.m03 - (the_detector._transmat.m03/sl) + (_temp_result.m03/sl);

						the_detector._transmat.m10 = the_detector._transmat.m10 - (the_detector._transmat.m10/sl) + (_temp_result.m10/sl);
						the_detector._transmat.m11 = the_detector._transmat.m11 - (the_detector._transmat.m11/sl) + (_temp_result.m11/sl);
						the_detector._transmat.m12 = the_detector._transmat.m12 - (the_detector._transmat.m12/sl) + (_temp_result.m12/sl);
						the_detector._transmat.m13 = the_detector._transmat.m13 - (the_detector._transmat.m13/sl) + (_temp_result.m13/sl);

						the_detector._transmat.m20 = the_detector._transmat.m20 - (the_detector._transmat.m20/sl) + (_temp_result.m20/sl);
						the_detector._transmat.m21 = the_detector._transmat.m21 - (the_detector._transmat.m21/sl) + (_temp_result.m21/sl);
						the_detector._transmat.m22 = the_detector._transmat.m22 - (the_detector._transmat.m22/sl) + (_temp_result.m22/sl);
						the_detector._transmat.m23 = the_detector._transmat.m23 - (the_detector._transmat.m23/sl) + (_temp_result.m23/sl);
						
					} else
						_detector.getTransmationMatrix(i, the_detector._transmat);
									
/// use ecevnt dispatch--
					_current_detected.push(the_detector);

					if( idx >= 0 )
					{	/// remove from saving array, the rest is undetected
						_last_detected.splice( idx, 1 );
						the_detector.dispatchEvent( new Event( FlarDetector.MARKER_UPDATED ) );
					} else
					{
						the_detector.dispatchEvent( new Event( FlarDetector.MARKER_ADDED ) );
						the_detector.dispatchEvent( new Event( FlarDetector.MARKER_UPDATED ) );	/// first time update
					}
/// --use ecevnt dispatch

				} catch (e:Error)
				{
					continue;
				}
			}
			
/// use ecevnt dispatch--
			var dec:FlarDetector;
			while( dec = _last_detected.pop() )
			{
				dec._detected = false;
				if( dec._delay == 0 && _force_delay == 0 )
				{
					dec.dispatchEvent( new Event( FlarDetector.MARKER_REMOVED ) );
				} else
				{
					var timer:Timer;
					if( _force_delay != 0 )
						timer = new Timer( _force_delay, 1 );	/// use force delay
					else
						timer = new Timer( dec._delay, 1 );		/// use owner delay
					timer.addEventListener(TimerEvent.TIMER_COMPLETE, dec.markerRemoved ); 
					timer.start();
				}
			}
/// --use ecevnt dispatch

		}
				
		public static function get number_of_square():int
		{
			return _number_of_square;
		}
		
		public static function getResult(idx:int):FLARMultiMarkerDetectorResult
		{
			return _detector.getResult(idx);
		}

		public static function getPattern( square:FLARSquare, i_width:Number = 16, i_markerPercentWidth:Number = 100 ):String
		{
			var colorPat:FLARDynamicRatioColorPatt_O3 = 
					new FLARDynamicRatioColorPatt_O3(i_width, i_width, i_markerPercentWidth/10, i_markerPercentWidth/10);
			colorPat.pickFromRaster(_raster, square);
			var code:FLARCode = new FLARCode(i_width, i_width);
			code.fromPattern(colorPat);
			return code.toString();
		}
		
		/// make sure last tow parameter 'width' should match the marker when you generate.
		public function registCode(i_stream:String, i_width:Number, i_markerPercentWidth:Number):void
		{
			_i_width = i_width;
			_i_markerPercentWidth = i_markerPercentWidth;
			this.unregistCode();
			_code = new FLARCode(i_width,i_width,i_markerPercentWidth,i_markerPercentWidth);
			_code.loadARPatt(i_stream);
			_codes.push( _code );
			_widths.push( i_width );
			_detectors.push(this);
			/// must init FLARMultiMarkerDetector			
			//if( _codes.length == 1 )	/// A: use this if you modify FLARMultiMarkerDetector.as at line 232, replace "_number_of_code" with "_codes.length"
			if( _codes.length > 0 )		/// a: use this if you do not modify FLARMultiMarkerDetector.as at line 232.
				_detector = new FLARMultiMarkerDetector(_param,_codes,_widths,_codes.length);
			//_detector.setContinueMode(true); /// it's not yet implemented and may cause transmat error
			this.dispatchEvent( new Event( FlarDetector.REGIST ) );
		}

		public function unregistCode():void
		{
			var idx:int = _detectors.indexOf( this );
			if( idx >= 0 )
			{
				_detectors.splice( idx, 1 );
				_codes.splice( idx, 1 );
				_widths.splice( idx, 1 );
				_code = null;
				//if( _codes.length == 0 )	/// B: use this if you modify FLARMultiMarkerDetector.as at line 232, replace "_number_of_code" with "_codes.length"
				//	_detector = null;
				if( _codes.length > 0 )		/// b: use this if you do not modify FLARMultiMarkerDetector.as at line 232
					_detector = new FLARMultiMarkerDetector(_param,_codes,_widths,_codes.length);
			}
			this.dispatchEvent( new Event( FlarDetector.UNREGIST ) );
		}
		
		public function loadCode( codeFile:String, i_width:Number=16, i_markerPercentWidth:Number=50 ):void 
		{
			_i_width = i_width;
			_i_markerPercentWidth = i_markerPercentWidth;
			_loader = new URLLoader();
			_loader.dataFormat = URLLoaderDataFormat.TEXT;
			_loader.addEventListener(Event.COMPLETE, onloadCodeCOMPLETE );
			_loader.load(new URLRequest(codeFile));
		}

		private function onloadCodeCOMPLETE( event:Event ):void
		{
			_loader.removeEventListener(Event.COMPLETE, onloadCodeCOMPLETE );
			this.registCode( _loader.data, _i_width, _i_markerPercentWidth );
		}

		public static function currentDetected():Array	// of FlarDetector
		{
			return _current_detected;
		}
		
		public function markerRemoved( event:TimerEvent ):void
		{
			if( !this._detected )
				this.dispatchEvent( new Event( FlarDetector.MARKER_REMOVED ) );
		}

		public function get detected():Boolean
		{
			return this._detected;
		}
		
		public function get result():FLARMultiMarkerDetectorResult
		{
			return this._result;
		}
		
		public function get transmat():FLARTransMatResult
		{
			return this._transmat;
		}
		
		public function get codeWidth():Number
		{
			return this._i_width;
		}
		
		public function get markerPercentWidth():Number
		{
			return this._i_markerPercentWidth;
		}

		public function toBitmap():BitmapData
		{
			var patBW:Array = this._code.getPatBW()[0];
			var w:int = this._code.getWidth();
			var h:int = this._code.getHeight();

			var bmp:BitmapData = new BitmapData( w, h );
			var bw:uint;
			var c:uint;
			for( var x:uint = 0; x < w; x++ )
			{
				for( var y:uint = 0; y < h; y++ )
				{
					bw = patBW[y][x]>0?0:255;
					c = (bw << 16) | (bw << 8) | bw;
					bmp.setPixel(x, y, c);
				}
			}
			return bmp;
		}
	}
}